#include "myreadline.h"

#define STATE_READING 0
#define STATE_UTILIZATION 1
#define STATE_EOF 2

#define RES_EOF 0
#define RES_SYS_ERR -1
#define RES_TOO_LONG_ERR -2

#define bool int
#define true 1
#define false 0

#define Index int
#define Offset int

static int blockSize(MY* my) {
    if ( my->head > my->tail || (my->head == my->tail && my->actualSize == my->bufSize)) {
//   ###t****h###
        return my->head - my->tail;
    } else {
//   ***h####t***
        return my->bufSize - my->tail;
    }

}


static Offset getOffset(MY* my, Index i) {
    return (my->head + i) % my->bufSize;
}

MY* myrl_fromfd (int fd) {
    MY* my = (MY*) malloc(sizeof(MY));
    bzero(my, sizeof(MY));
    my->fd = fd;
    my->bufSize = BUF_SIZE;
    return my;
}

static Index findNewLine(MY* my) {
    int i;
    for ( i=0; i<my->actualSize; i++) {
        if (my->buf[getOffset(my, i)]=='\n'||my->buf[getOffset(my, i)] == '\r') {
            return i;
        }
    }
    if (my->state == STATE_EOF && my->actualSize>0) {
        return my->actualSize;
    }

    return -1;
}

static void reset(MY* my) {
    my->actualSize = 0;
    my->head = 0;
    my->tail = 0;
}

static void popLine(MY* my, Index i) {
    if ( my->actualSize>i) {
        my->actualSize -= i+1;
        my->head = getOffset(my, i+1);
    } else {
        reset(my);
    }
}

static int writeAll(int fd, const char* msg, size_t len) {
    int offset = 0;
    while (len>0) {
        int res = write(fd, msg + offset, len);
        if (res<0) {
            return res;
        }
        offset += res;
        len -= res;
    }
    return 0;
}

static Index readLine(MY* my) {
    Index nl = 0;
    int bs = 0;
    while ((nl = findNewLine(my)) == -1 && (bs = blockSize(my)) > 0 ) {
        int res = read(my->fd, my->buf + my->tail, bs);
        if (res < 0) {
            return -2;
        }
        my->tail = (my->tail + res) % my->bufSize;
        my->actualSize += res;
        if (res == 0) {
            my->state = STATE_EOF;
            nl = findNewLine(my);
            if (nl==-1) {
                reset(my);
            }
            return nl==-1?-2011:nl;
        }
    }
    return nl;
}

static RES readToBuf(MY* my, char* buf, size_t len, Index nl) {
    Offset off = getOffset(my, nl);
    if (nl >= len) {
        my->state = my->state == STATE_EOF ? my->state : STATE_UTILIZATION;
        reset(my);
        return RES_TOO_LONG_ERR;
    }

    int i = 0;
    for ( i = 0; i < nl; i++) {
        buf[i] = my->buf[getOffset(my, i)];
    }

    popLine(my, nl);
    buf[nl] = '\n';
    return nl+1;
}

RES myrl_readline (MY* my, char* buf, size_t len) {
    switch (my->state) {
    case STATE_READING: {
        Index nl = readLine(my);
        if (nl == -2011) {
            return RES_EOF;
        }
        if (nl == -2) {
            return RES_SYS_ERR;
        }

        if (nl == -1 ) {
            my->state = STATE_UTILIZATION;
            reset(my);
            return RES_TOO_LONG_ERR;
        } else {
            return readToBuf(my, buf, len, nl);
        }
    } break;

    case STATE_UTILIZATION: {
        Index nl = 0;
        while ((nl = readLine(my)) == -1) {
            reset(my);
        }
        if (nl == -2011) {
            return RES_EOF;
        }
        popLine(my, nl);
        if (my->state != STATE_EOF) {
            my->state = STATE_READING;
        }
        return myrl_readline(my, buf, len);
    } break;

    case STATE_EOF: {
        Index nl = findNewLine(my);
        if (my->actualSize == 0) {
            return RES_EOF;
        } else {
            return readToBuf(my, buf, len, getOffset(my, findNewLine(my)));
        }
    } break;
    }
}

int myrl_writeline (MY* my, const char* buf, size_t len) {
    int res = writeAll(my->fd, buf, len);
    if (res<0) {
        return res;
    }
    if (buf[len-1]!='\n') {
        return writeAll(my->fd, "\n", 1);
    }
}

int myrl_close(MY* my) {
    close(my->fd);
    free(my);
}
